using System;
using System.Diagnostics;
using System.Net.NetworkInformation;
using System.Windows.Threading;

using agsXMPP.protocol.client;
using agsXMPP.protocol.extensions.caps;
using agsXMPP.protocol.iq.version;
using agsXMPP.Xml.Dom;

using VSTalk.Engine.Core.Disco;
using VSTalk.Engine.Core.Feed;
using VSTalk.Engine.Core.Notifications.SystemMessages;
using VSTalk.Logger;

using agsXMPP;
using agsXMPP.Net;

using VSTalk.Engine.Core.Notifications;
using VSTalk.Engine.Core.XmppConnection.PresenceObserver;
using VSTalk.Engine.Core.XmppConnection.RosterObserver;
using VSTalk.Model;
using VSTalk.Tools;

using agsXMPP.protocol.iq.disco;

using log4net;

using Version = agsXMPP.protocol.iq.version.Version;

namespace VSTalk.Engine.Core.XmppConnection
{
    public class ClientConnection : IClientConnection
    {
        private static readonly ILog _log = LoggerFactory.Create(typeof(ClientConnection));

        private readonly Client _client;

        private readonly XmppClientConnection _connection = new XmppClientConnection(SocketConnectionType.Direct);
        
        private XmppClientMessageProvider _provider;
        private IActionWrapper<object, Exception> _onError;
        private IActionWrapper<object, Exception> _onSocketError;
        private IActionWrapper<object, Element> _onAuthError;
        private PresenceObserver.PresenceObserver _presenceObserver;
        private RosterObesrver _rosterObserver;

        private const string VERSION = "2.2.1";
        private const string NAME = "VSTalk";
        private readonly Version _version = new Version()
        {
            Name = NAME,
            Ver = VERSION,
        };

        private INotificationQueue NotificationQueue
        {
            get { return AppServices.Get<INotificationQueue>(); }
        }

        public Client Client
        {
            get { return _client; }
        }

        public XmppClientConnection Connection
        {
            get { return _connection; }
        }

        public XmppClientMessageProvider Provider
        {
            get { return _provider; }
        }

        public ClientConnection(Client client)
        {
            _client = client;

            SetRosterObsererver();
            SetPresenceObserver();
            SetDiscoInfo();
            _provider = new XmppClientMessageProvider(this);
        }

        public void StartListen()
        {
            var dispatcher = Dispatcher.CurrentDispatcher;
            _onError = dispatcher.Wrap<object, Exception>(OnError);
            _connection.OnError += _onError.Exec;

            _onSocketError = dispatcher.Wrap<object, Exception>(OnSocketError);
            _connection.OnSocketError += _onSocketError.Exec;
            
            _onAuthError = dispatcher.Wrap<object, Element>(OnAuthError);
            _connection.OnAuthError += _onAuthError.Exec;

            _connection.OnIq += OnIq;
            // just for logs
            _connection.OnReadXml += (sender, xml) =>
            {
                var msg = string.Format("RECV: {0}", xml);
                _log.Info(msg);
                //Debug.WriteLine(msg);
            };
            _connection.OnWriteXml += (sender, xml) =>
            {
                var msg = string.Format("SEND: {0}", xml);
                _log.Info(msg);
                //Debug.WriteLine(msg);
            };
            _presenceObserver.StartListen();
            _rosterObserver.StartListen();
            _provider.StartListen();
        }

        public void StopListen()
        {
            _provider.StopListen();
            _rosterObserver.StopListen();
            _presenceObserver.StopListen();
            _connection.OnIq -= OnIq;
            _connection.OnAuthError -= _onAuthError.Exec;
            _connection.OnSocketError -= _onSocketError.Exec;
            _connection.OnError -= _onError.Exec;

            if (OnStopListen != null)
                OnStopListen(this, new EventArgs());
        }

        private void SetPresenceObserver()
        {
            _presenceObserver = new PresenceObserver.PresenceObserver(_connection);
            _presenceObserver.AddHandler(new AuthorizationPresenceHandler(_client));
        }

        private void SetRosterObsererver()
        {
            _rosterObserver = new RosterObesrver(_client, _connection);
        }

        private void OnSocketError(object arg1, Exception arg2)
        {
            _log.Error("Socket error", arg2);
            NotificationQueue.PushToFront(new InternalClientErrorMessage(arg2.Message));
        }

        internal void Connect()
        {
            _connection.Username = _client.Login;
            _connection.Resource = _client.Resource;
            _connection.Server = _client.Server;
            _connection.Password = _client.Password;
            _connection.UseSSL = false;
            _connection.RegisterAccount = false;
            _connection.AutoResolveConnectServer = true;
            _connection.UseCompression = false;
            _connection.EnableCapabilities = true;
            _connection.AutoPresence = true;
            _connection.AutoRoster = true;
            
            _log.InfoFormat("Connecting to {0}", _client.Id);
            _connection.Open();
        }

        private void SetDiscoInfo()
        {
            _connection.Capabilities = new Capabilities
            {
                Node = ExtUri.APP
            };
            

            var discoManager = new DiscoManager(_connection);
            _connection.DiscoInfo.AddIdentity(new DiscoIdentity("pc", "vstalk", "client"));

            _connection.DiscoInfo.AddFeature(new DiscoFeature(agsXMPP.Uri.CAPS));
            _connection.DiscoInfo.AddFeature(new DiscoFeature(agsXMPP.Uri.CHATSTATES));
            _connection.DiscoInfo.AddFeature(new DiscoFeature(agsXMPP.Uri.DISCO_INFO));
            _connection.DiscoInfo.AddFeature(new DiscoFeature(agsXMPP.Uri.DISCO_ITEMS));
            _connection.DiscoInfo.AddFeature(new DiscoFeature(agsXMPP.Uri.MUC));
            _connection.DiscoInfo.AddFeature(new DiscoFeature(agsXMPP.Uri.IQ_VERSION));
            _connection.DiscoInfo.AddFeature(new DiscoFeature(agsXMPP.Uri.PING));

            _connection.DiscoInfo.AddFeature(new DiscoFeature(ExtUri.XAML));
            _connection.UpdateCapsVersion();

            _connection.DiscoInfo.Node = string.Format("{0}#{1}", _connection.Capabilities.Node, _connection.Capabilities.Version);

            discoManager.AutoAnswerDiscoInfoRequests = true;
        }


        private void OnIq(object sender, IQ iq)
        {
            if (iq.Query != null && iq.Type == IqType.get && iq.Query.Namespace == agsXMPP.Uri.IQ_VERSION)
            {
                _connection.IqGrabber.SendIq(new IQ(IqType.result)
                {
                    Id = iq.Id,
                    To = iq.From,
                    From = _connection.MyJID,
                    Query = _version
                });
            }
            if (iq.Type == IqType.get && iq.FirstChild is agsXMPP.protocol.extensions.ping.Ping)
            {
                _connection.IqGrabber.SendIq(new IQ(IqType.result)
                {
                    Id = iq.Id,
                    From = _connection.MyJID,
                    To = iq.From,
                });
            }
        }

        protected void OnError(object sender, Exception ex)
        {
            Debug.WriteLine(ex);
            _log.Error("Unexpected error", ex);
            NotificationQueue.PushToFront(new InternalClientErrorMessage(string.Format("{0} : {1}", _client.Login, ex.Message)));
            _connection.Close();
        }

        internal void Disconnect()
        {
            _connection.Close();
        }

        private void OnAuthError(object sender, agsXMPP.Xml.Dom.Element rp)
        {
            var message = string.Format("Authentication failed {0}!", _client.Login);
            _log.ErrorFormat("{0} {1}", message, rp);
            NotificationQueue.PushToFront(new InternalClientErrorMessage(message));
            Disconnect();
        }

        public event EventHandler OnStopListen;
    }
}